package org.infodavid.common.impl.persistence;

import java.util.ArrayList;
import java.util.List;

import javax.persistence.PersistenceException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.Criteria;
import org.hibernate.criterion.Disjunction;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.joda.time.DateTime;

import org.infodavid.common.annotations.AImplementation;
import org.infodavid.common.annotations.AImplementation.ERuntime;
import org.infodavid.common.dto.IUserSessionListItem;
import org.infodavid.common.ejb.UserSessionBean;
import org.infodavid.common.model.IUser;
import org.infodavid.common.model.IUserSession;
import org.infodavid.common.model.criteria.IUserSessionCriteria;
import org.infodavid.common.model.impl.HibernateHelper;
import org.infodavid.common.persistence.IQueryParameter;
import org.infodavid.common.persistence.PersistenceSession;
import org.infodavid.common.persistence.impl.DataServiceHelper;
import org.infodavid.common.persistence.impl.PersistenceUtil;
import org.infodavid.common.services.exceptions.ServiceException;

/**
 * The Class UserSessionDataServiceImpl.
 */
@AImplementation(value = AbstractUserSessionDataService.class, runtime = ERuntime.DEFAULT)
public final class UserSessionDataServiceImpl extends AbstractUserSessionDataService {

	/** The Constant log. */
	private static final Log LOGGER = LogFactory.getLog(AbstractUserDataService.class);

	/** The helper. */
	private final DataServiceHelper<IUserSessionListItem,IUserSession,String> helper;

	/**
	 * The Constructor.
	 */
	private UserSessionDataServiceImpl() {
		super();

		helper = new DataServiceHelper<IUserSessionListItem,IUserSession,String>(this);
	}

	/*
	 * (non-Javadoc)
	 * @see org.infodavid.common.persistence.IDataService#getEntityClass()
	 */
	public Class<? extends IUserSession> getEntityClass() {
		return UserSessionBean.class;
	}

	/*
	 * (non-Javadoc)
	 * @see
	 * org.infodavid.common.persistence.IDataService#find(org.infodavid.common.persistence.PersistenceSession
	 * , java.util.List, java.util.List)
	 */
	public long find(final PersistenceSession session, final List<IUserSessionListItem> entities,
	    final List<IQueryParameter> parameters) throws PersistenceException {
		return helper.findListItems(session, entities, parameters, QUERY_FIND, QUERY_FIND_COUNT);
	}

	/*
	 * (non-Javadoc)
	 * @see
	 * org.infodavid.common.persistence.IDataService#find(org.infodavid.common.persistence.PersistenceSession
	 * , java.util.List, org.infodavid.common.model.criteria.ISearchCriteria)
	 */
	@SuppressWarnings("unchecked")
	public long find(final PersistenceSession session, final List<IUserSessionListItem> entities,
	    final IUserSessionCriteria criteria) throws PersistenceException {
		if (LOGGER.isDebugEnabled()) {
			LOGGER.debug("Searching by criteria using session = " + session + ", entities = " + entities
			    + ", criteria = " + criteria);
		}

		PersistenceUtil.validate(session);
		helper.validate(entities);

		final List<IQueryParameter> params = new ArrayList<IQueryParameter>();
		final HibernateHelper hbnHelper = HibernateHelper.getInstance();
		final Criteria hbnCriteria = hbnHelper.createCriteria(session, UserSessionBean.class);
		final Criteria hbnCountCriteria = hbnHelper.createCriteria(session, UserSessionBean.class);

		if (criteria == null) {
			hbnCriteria.add(Restrictions.eq(IUserSession.ARCHIVED, Boolean.FALSE));
			hbnCountCriteria.add(Restrictions.eq(IUserSession.ARCHIVED, Boolean.FALSE));
		}
		else {
			final DateTime expirationDate = new DateTime().minusMinutes(getSessionIdleTimeInMin());
			final Boolean archived = criteria.isArchived();
			final Boolean expired = criteria.isExpired() == null ? Boolean.FALSE : criteria.isExpired();
			final Long key = criteria.getUserKey();

			if (!(key == null || key.longValue() < 0)) {
				hbnCriteria.add(Restrictions.eq(IUserSession.USER + '.' + IUser.KEY, key));
				hbnCountCriteria.add(Restrictions.eq(IUserSession.USER + '.' + IUser.KEY, key));
			}

			if (archived == null) {
				hbnCriteria.add(Restrictions.eq(IUserSession.ARCHIVED, Boolean.FALSE));
				hbnCountCriteria.add(Restrictions.eq(IUserSession.ARCHIVED, Boolean.FALSE));
			}
			else {
				hbnCriteria.add(Restrictions.eq(IUserSession.ARCHIVED, archived));
				hbnCountCriteria.add(Restrictions.eq(IUserSession.ARCHIVED, archived));
			}

			if (LOGGER.isDebugEnabled()) {
				LOGGER.debug("Using expiration date = " + expirationDate);
			}

			if (expired.booleanValue()) {
				final Disjunction disjunction = Restrictions.disjunction();

				disjunction.add(Restrictions.eq(IUserSession.SIGNED_OUT, Boolean.TRUE));
				disjunction.add(Restrictions.le(IUserSession.LAST_ACCESS_DATE, expirationDate));

				hbnCriteria.add(disjunction);
				hbnCountCriteria.add(disjunction);
			}
			else {
				hbnCriteria.add(Restrictions.eq(IUserSession.SIGNED_OUT, Boolean.FALSE)).add(
				    Restrictions.gt(IUserSession.LAST_ACCESS_DATE, expirationDate));
				hbnCountCriteria.add(Restrictions.eq(IUserSession.SIGNED_OUT, Boolean.FALSE)).add(
				    Restrictions.gt(IUserSession.LAST_ACCESS_DATE, expirationDate));
			}
		}

		PersistenceUtil.applyCriteria(params, criteria);
		hbnHelper.applyParameters(params, hbnCriteria);

		try {
			hbnCountCriteria.setProjection(Projections.count(IUserSession.KEY));
			session.flush();

			final long count = ((Number)hbnCountCriteria.uniqueResult()).longValue();

			if (count > 0) {
				entities.addAll(hbnCriteria.list());
			}

			return count;
		}
		finally {
			PersistenceUtil.releaseQueryParameters(params);
		}
	}

	/*
	 * See super class or interface. (non-Javadoc)
	 * @see
	 * org.infodavid.common.persistence.IDataService#clear(org.infodavid.common.persistence.PersistenceSession
	 * , java.util.List)
	 */
	public void clear(final PersistenceSession session, final List<IQueryParameter> parameters)
	    throws PersistenceException {
		if (LOGGER.isDebugEnabled()) {
			LOGGER.debug("Removing entities using session = " + session + ", parameters = " + parameters);
		}

		session.removeEntities(session.findEntities(IUserSession.class, QUERY_FIND, parameters), null);
	}

	/*
	 * See super class or interface. (non-Javadoc)
	 * @see
	 * org.infodavid.common.persistence.IDataService#count(org.infodavid.common.persistence.PersistenceSession
	 * , java.util.List)
	 */
	public long count(final PersistenceSession session, final List<IQueryParameter> parameters)
	    throws PersistenceException {
		return helper.count(session, parameters, QUERY_FIND_COUNT);
	}

	/*
	 * (non-Javadoc)
	 * @see
	 * org.infodavid.common.persistence.IDataService#findByCreationDate(org.infodavid.common.persistence
	 * .PersistenceSession, org.joda.time.DateTime, org.joda.time.DateTime, java.util.List,
	 * java.util.List)
	 */
	public long findByCreationDate(final PersistenceSession session, final DateTime date1,
	    final DateTime date2, final List<IUserSessionListItem> entities,
	    final List<IQueryParameter> parameters) throws PersistenceException {
		PersistenceUtil.validate(session);

		final List<IQueryParameter> params = PersistenceUtil.initializeQueryParameters(parameters);

		try {
			PersistenceUtil.applySorting(params, IUserSession.CREATION_DATE);

			return helper.findListItemsByDate(session, date1, date2, entities, params,
			    QUERY_FIND_BY_CREATION_DATE, QUERY_FIND_BY_CREATION_DATE_COUNT);
		}
		finally {
			PersistenceUtil.releaseQueryParameters(params);
		}
	}

	/*
	 * See super class or interface. (non-Javadoc)
	 * @see
	 * org.infodavid.common.persistence.IDataService#findByModificationDate(org.infodavid.common.persistence
	 * .PersistenceSession, org.joda.time.DateTime, org.joda.time.DateTime, java.util.List,
	 * java.util.List)
	 */
	public long findByModificationDate(final PersistenceSession session, final DateTime date1,
	    final DateTime date2, final List<IUserSessionListItem> entities,
	    final List<IQueryParameter> parameters) throws PersistenceException {
		PersistenceUtil.validate(session);

		final List<IQueryParameter> params = PersistenceUtil.initializeQueryParameters(parameters);

		try {
			PersistenceUtil.applySorting(params, IUserSession.MODIFICATION_DATE);

			return helper.findListItemsByDate(session, date1, date2, entities, params,
			    QUERY_FIND_BY_MODIFICATION_DATE, QUERY_FIND_BY_MODIFICATION_DATE_COUNT);
		}
		finally {
			PersistenceUtil.releaseQueryParameters(params);
		}
	}

	/*
	 * (non-Javadoc)
	 * @see
	 * org.infodavid.common.persistence.UserSessionDataService#findLastActiveByUser(org.infodavid.common
	 * .persistence.PersistenceSession, java.lang.Long)
	 */
	@Override
	public IUserSession findLastActiveByUser(final PersistenceSession session, final Long userKey)
	    throws PersistenceException, ServiceException {
		if (LOGGER.isDebugEnabled()) {
			LOGGER.debug("Searching by user using session = " + session + ", userKey = " + userKey);
		}

		PersistenceUtil.validate(session);

		final List<IQueryParameter> params = PersistenceUtil.borrowQueryParameters(2);

		params.get(0).setName(IUser.ID).setValue(userKey);
		// the last session has been updated during the current day
		params.get(1).setName("date1").setValue(new DateTime().minusMinutes(getSessionIdleTimeInMin()));

		try {
			return helper.find(session, params, IUserSession.class, QUERY_FIND_LAST_ACTIVE_BY_USER);
		}
		finally {
			PersistenceUtil.releaseQueryParameters(params);
		}
	}
}
